/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */
package cz.mzk.kramerius.rukopisyimport.sourceData;

import cz.mzk.kramerius.commons.Dom4jUtils;
import cz.mzk.kramerius.commons.PropertyLoader;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.net.URL;
import java.util.HashMap;
import java.util.Map;
import java.util.Properties;
import java.util.logging.Level;
import java.util.logging.Logger;
import org.dom4j.Document;
import org.dom4j.DocumentException;
import org.dom4j.DocumentHelper;
import org.dom4j.Element;
import org.dom4j.Node;
import org.dom4j.XPath;

/**
 *
 * @author Martin Rehanek (rehan@mzk.cz)
 */
public class Marc21Downloader {

    private static final Logger logger = Logger.getLogger(Marc21Downloader.class.getName());
    private static final Map<String, String> prefixNamespaceMap = new HashMap<String, String>();
    private static final String PROP_SISNO_SIGNATURE_MAPPING_FILE = "SysnoSignatureTable";
    private static final String PROP_TMP_FILE = "Marc21Downloader.tmpFile";
    private static final String PROP_OAI_BASE = "Marc21Downloader.oaiBaseUrl";
    private static final String PROP_ALEPH_BASE = "Marc21Downloader.alephBase";
    private final File tmpFile;
    private final SignatureSysnoMapper signatureSysnoMapper;
    private final String oaiBase;
    private final String alephBase;

    static {
        prefixNamespaceMap.put("oai", "http://www.openarchives.org/OAI/2.0/");
        prefixNamespaceMap.put("marc21", "http://www.loc.gov/MARC21/slim");
    }

    public Marc21Downloader(Properties properties) {
        PropertyLoader propertyLoader = new PropertyLoader(properties);
        tmpFile = propertyLoader.loadFile(PROP_TMP_FILE, false, false, false);
        File mappingFile = propertyLoader.loadFile(PROP_SISNO_SIGNATURE_MAPPING_FILE, true, true, false);
        oaiBase = propertyLoader.loadString(PROP_OAI_BASE);
        alephBase = propertyLoader.loadString(PROP_ALEPH_BASE);
        signatureSysnoMapper = SignatureSysnoMapper.instanceOf(mappingFile, alephBase);
    }

    /**
     *
     * @param tei
     * @return true if marc21 record was downloaded and saved
     */
    public boolean createMarcRecord(String signature, File marcFile) {
        if (signatureSysnoMapper == null) {
            throw new IllegalStateException(SignatureSysnoMapper.class.getName() + " not loaded");
        }
        String sysno = getSysno(signature);
        if (sysno != null) {
            logger.log(Level.INFO, "found sysno ''{0}'' for signature ''{1}''", new Object[]{sysno, signature});
            return downloadMarc(sysno, marcFile);
        } else {
            logger.log(Level.SEVERE, "cannot find sysno for signature ''{0}''", signature);
            return false;
        }
    }

    private String getSysno(String signature) {
        return signatureSysnoMapper.getSysno(signature);
    }

    private boolean downloadMarc(String sysno, File marcFile) {
        String requestUrl = buildOaiGetRecordRequestUrl(sysno);
        Document doc = downloadXml(requestUrl);
        if (doc != null) {
            if (Dom4jUtils.saveDocument(doc, marcFile)) {
                logger.log(Level.INFO, "marc21 record saved into file ''{0}''", marcFile.getAbsolutePath());
                return true;
            } else {
                logger.log(Level.INFO, "cannot save marc21 record into file ''{0}''", marcFile.getAbsolutePath());
                return false;
            }
        } else {
            logger.log(Level.INFO, "cannot obtain marc21 document from url ''{0}''", requestUrl);
            return false;
        }
    }

    private String buildOaiGetRecordRequestUrl(String sysno) {
        StringBuilder builder = new StringBuilder();
        builder.append(oaiBase);
        builder.append("?verb=GetRecord&metadataPrefix=marc21&identifier=");
        builder.append("oai:aleph.mzk.cz:");
        builder.append(alephBase);
        builder.append('-');
        builder.append(sysno);
        return builder.toString();
    }

    private Document downloadXml(String requestUrl) {
        if (saveHttpResponse(requestUrl)) {
            try {
                return createDocumentFromHttpResponsFile();
            } catch (IOException ex) {
                logger.log(Level.SEVERE, null, ex);
                return null;
            }
        } else {
            return null;
        }
    }

    private boolean saveHttpResponse(String requestUrl) {
        //TODO: neukladat do souboru, ale jen do pameti!
        try {
            URL url = new URL(requestUrl.toString());
            BufferedReader in = new BufferedReader(new InputStreamReader(url.openStream()));
            PrintWriter out = new PrintWriter(tmpFile);
            String inputLine;
            while ((inputLine = in.readLine()) != null) {
                out.println(inputLine);
            }
            in.close();
            out.close();
            return true;
        } catch (IOException ex) {
            Logger.getLogger(Marc21Downloader.class.getName()).log(Level.SEVERE, null, ex);
            return false;
        }
    }

    private Document createDocumentFromHttpResponsFile() throws IOException {
        Document oaiResponse;
        try {
            oaiResponse = Dom4jUtils.loadDocument(tmpFile, false);
            XPath xpath = DocumentHelper.createXPath("/oai:OAI-PMH/oai:GetRecord/oai:record/oai:metadata/marc21:record");
            xpath.setNamespaceURIs(prefixNamespaceMap);
            Node marcRoot = xpath.selectSingleNode(oaiResponse);
            if (marcRoot != null) {
                Document result = DocumentHelper.createDocument((Element) marcRoot.detach());
                return result;
            } else {
                logger.log(Level.SEVERE, "cannot find marc21:record in oai-pmh document");
                return null;
            }
        } catch (FileNotFoundException ex) {
            logger.log(Level.SEVERE, "cannot find file ''{0}'' containing oai-pmh response", tmpFile.getAbsolutePath());
            return null;
        } catch (DocumentException ex) {
            logger.log(Level.SEVERE, "cannot load oai-pmh response from file ''{0}''", tmpFile.getAbsolutePath());
            return null;
        }
    }
}
